package de.hska.kundenverwaltung.db;

import static de.hska.util.EjbConstants.ERSTE_VERSION;
import static de.hska.util.EjbConstants.KEINE_ID;
import static javax.persistence.TemporalType.DATE;

import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.Version;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.hska.bestellverwaltung.db.Bestellung;
import de.hska.util.MessagesUtil;

// Alternativen bei @Inheritance
//   strategy=SINGLE_TABLE (=default), TABLE_PER_CLASS, JOINED
// Alternativen bei @DiscriminatorColumn
//   discriminatorType=STRING (=default), CHAR, INTEGER
@Entity
@Table(name="kunde")
@Inheritance
@DiscriminatorColumn(name="art", length=1)
@NamedQueries({
	@NamedQuery(name  = Kunde.FIND_KUNDEN,
                query = "SELECT k " +
				        "FROM   Kunde k"),
	@NamedQuery(name  = Kunde.FIND_KUNDEN_ORDER_BY_ID,
		        query = "SELECT k " +
				        "FROM   Kunde k " +
		                "ORDER BY k.id"),
	@NamedQuery(name  = Kunde.FIND_KUNDEN_BY_NACHNAME,
	            query = "SELECT k " +
				        "FROM   Kunde k " +
	            		"WHERE  k.nachname = :" + Kunde.PARAM_KUNDE_NACHNAME),
	@NamedQuery(name  = Kunde.FIND_KUNDEN_BY_NACHNAME_PREFIX,
   	            query = "SELECT k " +
   				        "FROM   Kunde k " +
   	            		"WHERE  k.nachname LIKE :" + Kunde.PARAM_KUNDE_NACHNAME_PREFIX),
    @NamedQuery(name  = Kunde.FIND_ALL_NACHNAMEN,
   	            query = "SELECT   DISTINCT k.nachname " +
   				        "FROM     Kunde k " +
   				        "ORDER BY k.nachname"),
    @NamedQuery(name  = Kunde.FIND_KUNDEN_BY_NACHNAME_FETCH_BESTELLUNGEN,
	            query = "SELECT   DISTINCT k " +
			            "FROM     Kunde k LEFT JOIN FETCH k.bestellungen " +
			            "WHERE    k.nachname = :" + Kunde.PARAM_KUNDE_NACHNAME + " " +
			            "ORDER BY k.id"),
	@NamedQuery(name  = Kunde.FIND_KUNDE_BY_ID_FETCH_BESTELLUNGEN,
	            query = "SELECT DISTINCT k " +
			            "FROM   Kunde k LEFT JOIN FETCH k.bestellungen " +
			            "WHERE  k.id = :" + Kunde.PARAM_KUNDE_ID),
	@NamedQuery(name  = Kunde.FIND_KUNDEN_BY_PLZ,
	            query = "SELECT k " +
				        "FROM   Kunde k " +
			            "WHERE  k.adresse.plz = :" + Kunde.PARAM_KUNDE_ADRESSE_PLZ),
	@NamedQuery(name  = Kunde.FIND_KUNDE_BY_USERNAME,
	            query = "SELECT k " +
				        "FROM   Kunde k " +
	            		"WHERE  k.username = :" + Kunde.PARAM_KUNDE_USERNAME)
})
public abstract class Kunde implements java.io.Serializable {
	protected static final Log LOG = LogFactory.getLog(Kunde.class);
	protected static final boolean DEBUG = LOG.isDebugEnabled();
	protected static final boolean TRACE = LOG.isTraceEnabled();
	
	public static final String PRIVATKUNDE = "P";
	public static final String FIRMENKUNDE = "F";
	
	static final String FIND_KUNDEN = "findKunden";
	static final String FIND_KUNDEN_ORDER_BY_ID = "findKundenOrderById";
	static final String FIND_KUNDEN_BY_NACHNAME = "findKundenByNachname";
	static final String FIND_KUNDEN_BY_NACHNAME_PREFIX = "findKundenByNachnamePrefix";
	static final String FIND_ALL_NACHNAMEN = "findAllNachnamen";
	static final String FIND_KUNDEN_BY_NACHNAME_FETCH_BESTELLUNGEN = "findKundenByNachnameFetchBestellungen";
	static final String FIND_KUNDE_BY_ID_FETCH_BESTELLUNGEN = "findKundeByIdFetchBestellungen";
	static final String FIND_KUNDEN_BY_PLZ = "findKundenByPlz";
	static final String FIND_KUNDE_BY_USERNAME = "findKundeByUsername";
	
	static final String PARAM_KUNDE_ID = "id";
	static final String PARAM_KUNDE_NACHNAME = "nachname";
	static final String PARAM_KUNDE_NACHNAME_PREFIX = "nachnamePrefix";
	static final String PARAM_KUNDE_ADRESSE_PLZ = "plz";
	static final String PARAM_KUNDE_USERNAME = "username";

	
	@Id
	@GeneratedValue(generator="kunde_sequence_name")
	@SequenceGenerator(name="kunde_sequence_name", sequenceName="kunde_k_id_seq", allocationSize=1)
	@Column(name="k_id", nullable=false)
	protected Long id = KEINE_ID;

	@Version
	protected int version = ERSTE_VERSION;

	@Column(length=32, nullable=false)
	protected String nachname = "";

	@Column(length=32)
	protected String vorname = "";
	
	@Column(nullable=false)
	private short kategorie;
	
	@Column(nullable=false)
	private double umsatz;
	
	@Column
	@Temporal(DATE)
	protected Date seit;
	
	@Column(length=32, unique=true)
	protected String username = "";

	@Column
	protected String password = "";
	
	@Column
	protected String email = "";
	
	@Enumerated
	@Column(name="creditcard")
	protected CreditCardType creditCard = CreditCardType.NONE;  // AMEX, MC, VISA; nicht: Discover

	@Column(name="creditcard_id")
	protected String creditCardId = "";

	@Embedded
	protected Adresse adresse;

	// Default: fetch=LAZY, keine Kaskadierungen
	// Alternativen: cascade={PERSIST}, cascade={MERGE}, cascade={REMOVE}, cascade={REFRESH}, cascade={c1, c2}, cascade={ALL}
	@OneToMany(mappedBy="kunde")
	@OrderBy("id ASC")
	protected List<Bestellung> bestellungen;

	@Column(length=128)
	protected String dateiname;
	
	public Kunde() {
		super();
	}

	protected Kunde(String nachname, String vorname, String email, Date seit) {
		super();
		this.nachname = nachname;
		this.vorname = vorname;
		this.email = email;
		this.seit = seit;
	}
	
	@PostPersist
	protected void logDbId() {
		setUsername("" + getId());

		if (DEBUG) {
			final String msg = MessagesUtil.getMessage("log.kundenverwaltung.kunde.create", id, this);
			LOG.debug(msg);
		}
	}

	@PostUpdate
	protected void logNeueVersion() {
		if (TRACE) {
			final String msg = MessagesUtil.getMessage("log.kundenverwaltung.kunde.update", id, version);
			LOG.trace(msg);
		}
	}

	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}

	public int getVersion() {
		return version;
	}
	public void setVersion(int version) {
		this.version = version;
	}

	public String getNachname() {
		return nachname;
	}
	public void setNachname(String nachname) {
		this.nachname = nachname;
	}

	public String getVorname() {
		return vorname;
	}
	public void setVorname(String vorname) {
		this.vorname = vorname;
	}
	
	public short getKategorie() {
		return kategorie;
	}

	public void setKategorie(short kategorie) {
		this.kategorie = kategorie;
	}

	public double getUmsatz() {
		return umsatz;
	}

	public void setUmsatz(double umsatz) {
		this.umsatz = umsatz;
	}

	public Date getSeit() {
		return seit;
	}

	public void setSeit(Date seit) {
		this.seit = seit;
	}
	
	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public CreditCardType getCreditCard() {
		return creditCard;
	}

	public void setCreditCard(CreditCardType creditCard) {
		this.creditCard = creditCard;
	}

	public String getCreditCardId() {
		return creditCardId;
	}

	public void setCreditCardId(String creditCardId) {
		this.creditCardId = creditCardId;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Adresse getAdresse() {
		return adresse;
	}
	public void setAdresse(Adresse adresse) {
		this.adresse = adresse;
	}

	public List<Bestellung> getBestellungen() {
		return bestellungen;
	}
	public void setBestellungen(List<Bestellung> bestellungen) {
		this.bestellungen = bestellungen;
	}

	abstract public String getArt();

	public String getDateiname() {
		return dateiname;
	}

	public void setDateiname(String dateiname) {
		this.dateiname = dateiname;
	}

	@Override
	public String toString() {
		return "id=" + id + ", version=" + version +
			   ", nachname=" + nachname + ", vorname=" + vorname + ", seit=" + seit +
			   ", email=" + email + ", creditCard=" + creditCard + ", creditCardId=" + creditCardId +
			   ", art=" + getArt() +
			   ", username=" + username + ", password=" + password + ", dateiname=" + dateiname;
	}

	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + ((adresse == null) ? 0 : adresse.hashCode());
		result = PRIME * result + ((nachname == null) ? 0 : nachname.hashCode());
		result = PRIME * result + ((seit == null) ? 0 : seit.hashCode());
		result = PRIME * result + ((vorname == null) ? 0 : vorname.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final Kunde other = (Kunde) obj;
		if (email == null) {
			if (other.email != null)
				return false;
		} else if (!email.equals(other.email))
			return false;
		if (nachname == null) {
			if (other.nachname != null)
				return false;
		} else if (!nachname.equals(other.nachname))
			return false;
		if (adresse == null) {
			if (other.adresse != null)
				return false;
		} else if (!adresse.equals(other.adresse))
			return false;
		if (seit == null) {
			if (other.seit != null)
				return false;
		} else if (!seit.equals(other.seit))
			return false;
		if (vorname == null) {
			if (other.vorname != null)
				return false;
		} else if (!vorname.equals(other.vorname))
			return false;
		return true;
	}
}